package com.example.basic.aop;

import com.example.basic.exception.CustomException;
import com.example.basic.service.RedisService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;

/**
 * 没有自动续约、方法断开后死锁没有解决，只是用了EXPIRE=30s
 */
@Aspect
@Component
public class LockAspect {

    private ExpressionParser parser = new SpelExpressionParser();
    private ParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    public static final ThreadLocal<String> LOCK_NAME = new ThreadLocal<>();
    public static final ThreadLocal<Boolean> LOCK_FLAG = new ThreadLocal<>();

    @Resource
    private RedisService redisService;

    @Around(value = "@annotation(lock)")
    public Object around(ProceedingJoinPoint joinPoint, Lock lock) throws Throwable {
        // 获取注解的方法
        String lockKey = getLockKey(joinPoint, lock);
        System.out.println(lockKey);
        // 获取锁
        Boolean success = redisService.setnx(lockKey, "1");
        if (Boolean.TRUE.equals(success)) {
            LOCK_NAME.set(lockKey);
            LOCK_FLAG.set(Boolean.TRUE);
            return joinPoint.proceed();
        } else {
            throw new CustomException("777777", "Redis Lock Already Exists, Set Failed!");
        }
    }

    @AfterReturning(value = "@annotation(lock)")
    public void release(Lock lock) {
        if (LOCK_FLAG.get()) {
            // 释放锁
            redisService.delete(LOCK_NAME.get());
        }
    }


    @AfterThrowing(value = "@annotation(lock)")
    public void releaseWithException(Lock lock) {
        if (LOCK_FLAG.get()) {
            // 释放锁
            redisService.delete(LOCK_NAME.get());
        }
    }

    // 获取LockKey
    private String getLockKey(JoinPoint joinPoint, Lock lock) {
        List<String> keyList = getKeyList(joinPoint, lock);
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        return String.format("%s.%s.%s", signature.getDeclaringTypeName(), signature.getMethod().getName(),
                StringUtils.collectionToDelimitedString(keyList, "", "-", ""));
    }

    // 获取锁KEY名称
    private List<String> getKeyList(JoinPoint joinPoint, Lock lock) {
        List<String> keyList = new ArrayList<>();
        Method method = getMethod(joinPoint);
        List<String> definitionKeys = getSpelDefinitionKey(lock.keys(), method, joinPoint.getArgs());
        keyList.addAll(definitionKeys);
        List<String> parameterKeys = getParameterKey(method.getParameters(), joinPoint.getArgs());
        keyList.addAll(parameterKeys);
        return keyList;
    }

    // 获取拦截方法
    private Method getMethod(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        if (method.getDeclaringClass().isInterface()) {
            try {
                method = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(),
                        method.getParameterTypes());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return method;
    }

    // 获取方法定义KEY
    private List<String> getSpelDefinitionKey(String[] definitionKeys, Method method, Object[] parameterValues) {
        List<String> definitionKeyList = new ArrayList<>();
        for (String definitionKey : definitionKeys) {
            if (definitionKey != null && !definitionKey.isEmpty()) {
                EvaluationContext context =
                        new MethodBasedEvaluationContext(null, method, parameterValues, nameDiscoverer);
                Object value = parser.parseExpression(definitionKey).getValue(context);
                if (value != null) {
                    definitionKeyList.add(value.toString());
                }
            }
        }
        return definitionKeyList;
    }

    // 获取参数KEY
    private List<String> getParameterKey(Parameter[] parameters, Object[] parameterValues) {
        List<String> parameterKey = new ArrayList<>();
        for (int i = 0; i < parameters.length; i++) {
            if (parameters[i].getAnnotation(LockKey.class) != null) {
                LockKey keyAnnotation = parameters[i].getAnnotation(LockKey.class);
                if (keyAnnotation.value().isEmpty()) {
                    parameterKey.add(parameterValues[i].toString());
                } else {
                    StandardEvaluationContext context = new StandardEvaluationContext(parameterValues[i]);
                    Object value = parser.parseExpression(keyAnnotation.value()).getValue(context);
                    if (value != null) {
                        parameterKey.add(value.toString());
                    }
                }
            }
        }
        return parameterKey;
    }
}
